/*
 * Script for dealing with the input and drawing the L-System.
 */

(function () {
    var canvas = document.getElementById("lsystemCanvas"),
        width = canvas.width,
        height = canvas.height,
        ctx = canvas.getContext("2d"),
		expandedArray,
		scaledLength,
		angle,
		ruleArray,
		
		/**
		 * Performs the necessary computations and then draws
		 * the fractal on the canvas object
		 */
		setUp = function(axiom, rule, iterations) {
			var expansionHolder = [],
				scaleFactor;
			
			iterations = parseInt(iterations);
			
			// Begin by changing the given degrees to rads
			angle = parseInt($("#angle").val()) * (Math.PI / 180);
			
			// Check to make sure the angle was OK
			if (!angle) {
				alert("JOO SUX AT ANGLES: USE TEH DEGREES, JUST TEH NUMBWER");
				return;
			}
			
			// Parse the rule into an array
			// Begin by removing whitespace from the original
			rule = rule.split(' ').join("");
			ruleArray = rule.split("");
			
			// Now, based on the rule, determine the scaling factor
			scaleFactor = validateRuleAndScale();
			
			// If there was a problem with the validation, we'll report
			// it here
			if (scaleFactor === -1) {
				alert("JOO SUX AT ROOLZ: USE F, -, +, [, ]");
				return;
			}
			
            // Next, make sure we take into account the number of iterations
            // for the scaling
            scaleFactor = Math.pow(scaleFactor, iterations);
			
			// Lastly, multiply the scaleFactor by the length to get the
            // scaledLength
            scaledLength = (4 / 5) * (width) * scaleFactor;
			
			// Start expanding the rule by the number of iterations
			expandedArray = axiom.split(' ').join("");
			
			// Go through the expanded array, replacing each "F" with the rule
			for(var i = 0; i < iterations; i++) {
				for(var j = 0; j < expandedArray.length; j++) {
					if (expandedArray[j] === "F") {
						expansionHolder = expansionHolder.concat(ruleArray);
					} else {
						expansionHolder.push(expandedArray[j]);
					}
				}
				expandedArray = expansionHolder;
				expansionHolder = [];
			}
			
		},
		
		/**
		 * Returns the scaling factor OR an error if the rule was
		 * malformed
		 */
		validateRuleAndScale = function() {
			var inPushPop = 0,
		        totalLength = 0.0,
				totalSegments = 0.0,
				currentAngle = 0.0;
			// Go through each element in the rule array, checking
			// for validity and then computing the scaling factor
			for(var i = 0; i < ruleArray.length; i++) {
				switch(ruleArray[i]) {
					case 'F': 
						if (inPushPop === 0) {
							totalSegments++;
							totalLength += Math.cos(currentAngle);
						} break;
					case '-': 
					    if (inPushPop === 0) {
							currentAngle -= angle;
						} break;
					case '+': 
					    if (inPushPop === 0) {
                            currentAngle += angle;
                        } break;
					case '[': inPushPop++; break;
					case ']': inPushPop--; break;
					default: return -1;
				}
			}
			
			// Next, make sure we had matching push-pops
            if (inPushPop !== 0) {
                return -1;
            }
            
            // Hack: Makes sure a rule doesn't divide by 0
            if (totalLength === 0) {
                return (1 / totalSegments);
            }
            
            // Or, we're good, so return the scaling factor
            return (1 / totalLength);
		},
		
		/**
		 * Perform the actual canvas drawing
		 */
		draw = function() {
			var stateStack = [], // Holds 3-tuples of form: [angle, x, y]
			    currentPos = [0, width / 10 , height / 2]
				nextPos = currentPos;
			
			ctx.beginPath();
			ctx.moveTo(currentPos[1], currentPos[2]);
			for (var i = 0; i < expandedArray.length; i++) {
				currentPos = nextPos;
				switch (expandedArray[i]) {
					case 'F':
					    nextPos[1] += scaledLength * Math.cos(currentPos[0]);
                        nextPos[2] += scaledLength * Math.sin(currentPos[0]);
					    
                        ctx.lineTo(nextPos[1], nextPos[2]);
					    break;
					case '-':
					    nextPos[0] += angle; 
					    break;
					case '+': 
                        nextPos[0] -= angle; 
                        break;
					case '[':
					    stateStack.push(currentPos.slice());
						break;
					case ']': 
					    nextPos = stateStack.pop();
						ctx.moveTo(nextPos[1], nextPos[2]);
						break;
					default: return;
				}
			}
			ctx.moveTo(width / 10, height / 2);
			ctx.closePath();
            ctx.stroke();
		};
		
	$("#submit").click(function() {
		ctx.clearRect(0, 0, width, height);
        setUp($("#axiom").val(), $("#rule").val(), $("#iterations").val());
		draw();
    });
	
}());
